Skip to content

feat: add rattler_vfs crate#2566

Draft
chrisburr wants to merge 23 commits into
conda:mainfrom
chrisburr:main
Draft

feat: add rattler_vfs crate#2566
chrisburr wants to merge 23 commits into
conda:mainfrom
chrisburr:main

Conversation

@chrisburr

@chrisburr chrisburr commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Description

This builds on top of #2565 and the work on @Dagmar-Dinjens to add a rattler_fs crate. See #1182 for the original concept. Basically the idea is to serve data directly from the rattler cache directory using a virtual filesystem, therefore avoiding the overhead of actually making environments. The usual install-time tricks (prefix-subsitution, python entrypoints, shebang-fixing, apple's code signing, ...) are applied on-the-fly as the data is read.

I see several motivations for this but the main ones are:

  • Using environments in large scale distributed systems without the overhead of installing environments while being able to make use of a pre-existing rattler cache directory.
  • Running on systems with slow filesystems (e.g. NFS) where environment creation is slow due to limited IOPS.
  • Avoiding the disk space overhead of having many pixi environments locally, many of which are used infreqently.

This PR includes 3 VFS backends for RattlerFS which are supported on the following platforms:

Platform FUSE NFS ProjFS
Linux N/A
macOS N/A
Windows N/A

We default to FUSE on Linux, NFS on macOS and ProjFS on Windows which allows all versions to run in user space assuming the VFS drivers are available. Windows is notably less extensively tested than the others. I also looked into FSKit on macOS but it seems that it's not yet suitable for something like RattlerFS.

This PR also contains the concept of a writable overlay for FUSE and NFS which then allows for edits to be made on top of the read-only underlying environment. This also makes it possible to pip install packages on top, though in principle RattlerFS could be expanded to support wheels natively rather than needing the overlay for that case. I've laid the ground work for this but didn't finish it.

The main component here is the rattler_fs crate which is used by the draft Pixi PR (prefix-dev/pixi#6548). There is a rattler mount command added to rattler_bin with an optional feature. I could see this as being useful in it's own right by systems which want to mount an environment from a lock file but don't want all of the the workspace functionality of pixi.

How Has This Been Tested?

There are End-To-End tests included which have been useful during development to make sure all three platforms continue to work. Also see prefix-dev/pixi#6548.

AI Disclosure

  • This PR contains AI-generated content.
    • I have tested any AI-generated content in my PR.
    • I take responsibility for any AI-generated content in my PR.

There have been far too many sessions involved in the creation of this to track exactly what tools have been used and which parts are hand written vs AI generated. It's more AI-generated than I would like but equally it contains far more functionality and edge case handling than I would have managed to find time for alone. This project represents most of my rust experience so I'm not in a position to evaluate the quality of the code but I think it's in a reasonable state all things considered.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added sufficient tests to cover my changes.

@chrisburr chrisburr changed the title Add RattlerFS crate feat: add rattler_fs crate Jul 7, 2026
DagmarFiep and others added 10 commits July 8, 2026 01:14
improved permission checking,
improved error message,
improved slice usage in link.rs,
Still have to look at the v2 backwards incompatibility comment
Finalise the offset-based prefix replacement: a structured Offsets enum (text
vs binary), memchr-based NUL scanning, and the
`copy_and_replace_placeholders_with_offsets` family, so install (and other
consumers) can replace the install prefix at known byte offsets instead of
rescanning each file. The `offsets` field on PathsEntry/PrefixPlaceholder is
optional and skipped when absent, so paths.json stays backward-compatible
(paths_version unchanged).
@baszalmstra

baszalmstra commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

This is pretty amazing work! Thanks @chrisburr ! Coincidentally @Dagmar-Dinjens is also working on this in #2543.

Ill be going over the code!

@chrisburr chrisburr marked this pull request as ready for review July 8, 2026 09:05
@chrisburr

Copy link
Copy Markdown
Contributor Author

I guess you meant @Dagmar-Dinjens? (Hi 👋! I'm glad to see the internship worked out!) I'd missed #2543 and I fear that PR is proabably orthogonal to this one...

If you'd like to chat about this PR on a call I'm happy to do so as it might be easier for getting up to speed as there is a lot going on here.

@baszalmstra baszalmstra left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a thorough review and tested the implementation on my windows machine. I think the code is already in a good state and apart from some tweaks here and there I would be fine if we merge this soon and then iterate on it in followup PRs.

Unfortunately I think this far surpasses the work that @Dagmar-Dinjens has pushed so far. @chrisburr I propose that we get this merged and let @Dagmar-Dinjens continue with followups? Would be happy to jump on a call to discuss this in more detail if that helps

I left way more comments than what I think is actually required to merge this. I think a lot of them we can address in follow-up PRs.

My main blockers are:

  • I think we should name the crate rattler_vfs instead of rattler_fs. But I can be persuaded.
  • We should not yet publish this crate automatically. This requires annotating the Cargo.toml with package.publish = false.

Btw why is windows nfs not supported?

Comment thread crates/rattler_vfs/src/overlay_fs/inode.rs
pub(crate) struct UpperInodeMap {
path_to_ino: HashMap<PathBuf, u64>,
ino_to_path: HashMap<u64, PathBuf>,
next_ino: AtomicU64,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of this struct requires locking for concurrency, so what does an atomic u64 buy us here.

Comment thread crates/rattler_vfs/src/overlay_fs/inode.rs
Comment thread crates/rattler_vfs/src/codesign.rs
Comment on lines +32 to +37
# NFS transport (optional)
nfs3_server = { version = "0.10", optional = true }
nfs3_types = { version = "0.5", optional = true }

[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.62", features = ["Win32_Storage_ProjectedFileSystem", "Win32_Foundation", "Win32_System_LibraryLoader"] }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets use workspace dependencies.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dagmar-Dinjens If you can look into this perhaps.

Comment thread Cargo.toml Outdated
dbus-secret-service-keyring-store = { version = "1.0.0", default-features = false }
dunce = "1.0.5"
enum_dispatch = "0.3.13"
env_logger = "0.11.9"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this used?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can just go @chrisburr ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is left over from an earlier version of this before I figured out how to interface pixi more cleanly.

Comment thread crates/rattler-bin/src/commands/mount.rs
Comment thread crates/rattler_vfs/src/fuse_adapter.rs
@@ -0,0 +1,691 @@
//! NFS transport adapter.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why do we use nfs3 instead of nfs4?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the best supported crate I found only had v3 and I wasn't aware of anything about v4 really mattering.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thing to add here is https://github.com/facebook/sapling/blob/61b3cde3bffdd7553094a9b4738af369abdf935b/eden/fs/docs/macOS.md

In general I found EdenFS's documentation to be a useful reference. For example the Windows docs were what made me finally give up on trying to support the read-only flag for ProjFS.

Comment thread crates/rattler_vfs/src/vfs_ops.rs
@baszalmstra

baszalmstra commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Also some notes from fable worth looking at:

Review: #2566feat: add rattler_fs crate

What it does

Adds a new rattler_fs crate that serves conda environments as virtual filesystems (FUSE on Linux/macOS, a localhost NFS server, ProjFS on Windows) directly from the package cache, applying install-time transforms — prefix replacement, Python entry points, shebang fixes, macOS ad-hoc code signing — on the fly during reads instead of materializing files on disk. A writable overlay layer (whiteouts + copy-up) lets users edit files and pip install on top of the read-only view. Supporting changes: paths.json gains optional offsets/shebang_length fields, link.rs gains offset-based replacement variants, plus a mount CLI command, an e2e nushell harness, and a CI workflow.

Blocking issues

1. The link.rs change corrupts real installs (not just mounts)

copy_and_replace_textual_placeholder_offsets (crates/rattler/src/install/link.rs:895) strips the shebang line and rebases source_bytes = rest, but then applies offsets that are absolute file positions. Every offset is now wrong by the shebang length: the loop injects the prefix mid-body and, for offsets near EOF, &source_bytes[last_match..offset] panics with an out-of-range slice. Any package that ships the new offsets field and has a shebang'd text file installs corrupted or crashes the installer. This is in the production install path, gated only by whether offsets is populated. The one test combining shebang + offsets passes an empty offsets vec, so it never triggers. This is the most important finding — it's a regression in already-shipping code.
https://github.com/conda/rattler/pull/2565/changes#r3544983267

2. Untrusted paths.json offsets panic inside filesystem callbacks

offsets comes straight from a package's paths.json (attacker-controllable) and is never validated. Empty binary groups panic at group.split_at(group.len() - 1) (usize underflow); out-of-range/out-of-order offsets panic at &source[src_start..src_end]. A package with "offsets": [[1000, 2000]] on a 10-byte file kills the FUSE/NFS read thread → whole mount dies. The identical arithmetic in the install-time variants panics the installer. Needs a validation pass (sorted, in-bounds, spaced ≥ placeholder length) at build/mount time.
https://github.com/conda/rattler/pull/2565/changes#r3544991157

3. Overlay copy-up has three independent data-corruption bugs

All in overlay_fs.rs::copy_to_upper, all untested, all hit by ordinary pip install/mv on a conda env:

  • Symlinks are copied up as empty regular files — only Directory is special-cased; a symlink's content_source returns ENOENT, falls to read (returns vec![] for symlinks), and fs::write creates a 0-byte file that permanently shadows the link. Conda envs are full of libfoo.so → libfoo.so.1.
  • Directory rename loses lower children — the upper_path.exists() early-return fires before the directory-recursion check, so a partially-materialized dir isn't recursed; renaming it drops any lower-only children, then a whiteout hides the originals.
  • Exec bit dropped on copy-upfs::write creates 0644 with no permission copy, so chmod/mv/truncate on bin/python yields a non-executable copy.

Plus a lookup/opaque ordering bug (overlay_fs.rs:308): lookup returns ENOENT for opaque dirs before checking the upper layer, so a freshly created file in a reinstalled package dir is visible via readdir but not stat/open — breaking the exact pip uninstall/reinstall pattern the feature targets.
#2582

4. NFS server has no per-user access control (security)

Two independent findings converge here. The server binds 127.0.0.1 (remote hosts excluded — good), but that bind is the only gate: NFS AUTH_UNIX is unauthenticated, the adapter never consults caller credentials, and access via raw RPC bypasses the mount point's Unix permission bits. Any other local user can speak NFS3 to the port and read (and, on writable mounts, write/delete) another user's environment, forging any uid. Compounding it, NFS wire filenames aren't sanitized: CREATE(dir, "../../../home/user/.bashrc") escapes the overlay directory via overlay_dir.join(...). A loopback-TCP NFS server is inherently shared among local users; at minimum this needs prominent documentation, ideally peer-credential rejection.
#2579

5. Packaging will break the release pipeline

rattler_fs has no description and no publish = false, and release-plz release runs on every push to main → publish fails (crates.io requires a description). Separately, rattler-bin (a published crate) now has rattler_fs = { path = "../rattler_fs", optional = true } with no version → cargo publish of rattler-bin fails. Convention is to add the crate to the pinned internal-crate list in the workspace Cargo.toml and use workspace = true.

Major issues (fix before real-world use)

  • Mount-path-longer-than-placeholder panics the read path. binary_ranged_read has assert!(new_prefix.len() <= old_prefix.len()); on Linux it's called directly, so mounting a short-placeholder package (e.g. the 32-char /opt/anaconda1anaconda2anaconda3) at a longer path aborts the FUSE thread. The installer returns a graceful InvalidData error for the same case.
  • VFS diverges from the installer in ways that break environments: vfs: VFS diverges from the installer in ways that break environments #2580
  • macOS codesign can't be byte-identical. Install shells out to /usr/bin/codesign (rebuilds the whole superblob, changes size/bytes); adhoc_resign only patches page hashes and only the first CodeDirectory (dual-signed / SHA-1-first binaries fail or go stale). This breaks the parity premise for signed binaries and is undocumented/untested.
  • codesign slice panics on malformed Mach-O — inconsistent n_code_slots vs code_limit produces &data[4096..100] → panic in the read handler. ~~ vfs: Replace codesigning with standardized implementation #2571
  • ProjFS enumeration sort uses str::cmp instead of PrjFileNameCompare (Windows case-insensitive collation); mis-ordering can make ProjFS drop or duplicate directory entries. vfs: enumeration sort uses str::cmp instead of PrjFileNameCompare #2581
  • ProjFS /DELAYLOAD never reaches the binarybuild.rs's rustc-link-arg applies only to rattler_fs's own targets, not to rattler.exe. rattler-bin has no build.rs, so the Windows binary still hard-imports projectedfslib.dll and dies at startup on machines without ProjFS — defeating the ProjFsDllMissing graceful-degradation path. vfs: dynamic load projfs api #2572

Performance (will dominate real-world experience)

  • ~~O(N²) ranged reads. Both text_ranged_read and binary_ranged_read walk the output stream byte-by-byte from position 0 on every call. Reading a 500 MB .so through FUSE in 128 KB chunks is ~terabyte-scale byte work. Offsets are sorted → binary-search to the start position and extend_from_slice the spans.~~ vfs: O(N²) ranged reads #2583
  • Eager full-file scan at mount time. offsets is a new rattler-only extension absent from essentially all real packages, so VirtualFS::with_platform falls back to fs::read on every prefix-bearing file (all the .sos and binaries), serially, on the caller's thread — defeating the "instant no-copy mount" goal. vfs: Eager full-file scan at mount time #2584
  • Overlay metadata: full-JSON rewrite + fsync per op under the state mutex → O(N²) for a bulk pip uninstall, blocking all concurrent lookups. vfs: Overlay metadata: full-JSON rewrite + fsync per op #2585

Minor / cleanup

Accidental unused workspace deps (env_logger, and the obscure mem = "0.5.0" — looks like a mangled memchr edit, remove it); memchr added to rattler_conda_types/Cargo.toml but unused there; CI path filter omits crates/rattler-bin/** and root pixi.toml (so a mount.rs-only PR skips the e2e); action pins lag repo convention (checkout v4 vs v6); mutex .lock().unwrap() poisoning throughout turns one panic into a mount-wide outage; single non-looping read() treats a short read as EOF; stat-failure fabricates a 0-byte file instead of EIO; overlay copy-up isn't crash-safe (no tmp+rename+fsync on the data path, unlike the metadata path); .rattler_fs_state.json is reachable and writable from inside the mount; PathType::Directory entries modeled as file leaves give inconsistent stat/readdir; VirtualFS::new is pub but takes a pub(crate) type; MountHandle::Fuse(fuser::...) leaks fuser into the public API (SemVer hazard).

Test coverage — the core weakness

The unit and parity suites are good but cover only internally-consistent happy paths. Nothing exercises: shebang files (finding 1 & shebang divergence), malformed offsets (finding 2), overlay symlinks / dir-rename-with-children / exec-bit / lookup-in-opaque-dir (finding 3), adversarial NFS filenames (finding 4), mount-path-longer-than-placeholder, Windows entry-point layout, clobbering, dual-CD codesign, or any of the three adapters at unit level. The e2e nushell harness is rigorous (I had an agent verify its pass/fail detection empirically) but validates behavior, not these boundaries. Adding cases in these gaps is where the real hardening is.
#2586

Recommendation

Request changes. The concept and structure are excellent and worth landing, but I'd block on: (1) the link.rs shebang+offsets install corruption, (2) untrusted-offset panic validation, (3) the three overlay copy-up corruptions + opaque-lookup bug, (4) the NFS access-control/path-traversal exposure (at least documented, ideally fixed), and (5) the release-pipeline breakage. The mount-path panic, VFS/installer divergences (shebang, clobber, Windows entry points, pypi), and the two structural performance issues should follow before this is used on real environments at scale. Given the surface area, splitting the paths.json/link.rs offset work into its own reviewed PR (it's independently useful and independently risky) would de-risk the rest.

@chrisburr

Copy link
Copy Markdown
Contributor Author

@chrisburr I propose that we get this merged and let @Dagmar-Dinjens continue with followups? Would be happy to jump on a call to discuss this in more detail if that helps

Yes, I am very happy to have @Dagmar-Dinjens pick this up again, especially as I'd like to spend some time on the package cache itself to help with distribution via CVMFS which is orhtogonal but useful for this.

If you want to arrange a chat, the conda-forge Zulip is probably the most reliable way for you to get my attention.

I left way more comments than what I think is actually required to merge this. I think a lot of them we can address in follow-up PRs.

How do you want to proceed on that side? I'll work my way though replying and implement some of them? Or do you want to use the comments as a chance to onboard @Dagmar-Dinjens?

I think we should name the crate rattler_vfs instead of rattler_fs. But I can be persuaded.

I'm not sure how many people know VFS as a concept. That said, the name has been in my brain for a long time so the problem might just be that it's different from what I've become used to. I don't have a strong opinion either way.

We should not yet publish this crate automatically. This requires annotating the Cargo.toml with package.publish = false.

👍

Btw why is windows nfs not supported?

Initially I tried to use only NFS and just ProjFS for later but I ran into a bunch of limitations. My memory is a bit fuzzy as this was a couple of months ago I think the main ones were:

  • it couldn't satisfy the pixi use case in of mounting the environment in %CWD%/.pixi/... without hacky tricks
  • limited to ~20 mounts before you run out of drive letters

@baszalmstra

Copy link
Copy Markdown
Collaborator

How do you want to proceed on that side? I'll work my way though replying and implement some of them? Or do you want to use the comments as a chance to onboard @Dagmar-Dinjens?

I have created issues for all the non-blocking items in this PR (prefixed with vfs:). I also updated the fable review. So all the items that are still open should be tackled in this PR. I tagged @Dagmar-Dinjens in some of them, I hope she can take care of those? @chrisburr Would you be able to take care of the remaining ones?

Once merged we can work on the individual issues. We can simply assign people to make sure we dont work on the same things.

Does that help/make sense?

I'm not sure how many people know VFS as a concept. That said, the name has been in my brain for a long time so the problem might just be that it's different from what I've become used to. I don't have a strong opinion either way.

So I asked ChatGPT for the deciding vote:

I’d vote rattler_vfs.

Why:

rattler_fs sounds like a broad filesystem utility crate for Rattler: path helpers, atomic writes, cache layout helpers, symlink handling, file locking, etc.

This implementation is much more specific: it presents conda environments as a virtual filesystem, backed by FUSE/NFS/ProjFS, with on-the-fly prefix replacement, generated files, code signing handling, and overlays. The PR itself describes it as serving data “using a virtual filesystem” and having “3 VFS backends.” The changed files also include vfs_ops.rs and virtual_fs.rs, which makes vfs the more precise abstraction.

@chrisburr Does that resonate with you?

chrisburr added 4 commits July 9, 2026 13:42
Correct the doc comments on `Offsets`, `PrefixPlaceholder::offsets` and
`PrefixPlaceholder::shebang_length` to match the draft CEP "Prefix placeholder
offsets in paths.json" (conda#2565):

- `offsets` exclude occurrences inside the shebang region, and for binary
  files the group's last value is the NUL terminator offset or the file size
  when the final C string is unterminated at end-of-file.
- `shebang_length` is present if and only if `offsets` is present, `file_mode`
  is text, and the file starts with `#!` — independent of whether the first
  line contains the placeholder.
- The `Offsets` shape is determined normatively by `file_mode`, not inferred
  from the JSON structure.
Bring install-time prefix replacement into line with the draft CEP "Prefix
placeholder offsets in paths.json" (conda#2565). `offsets` are
absolute byte positions that exclude the shebang region and are spliced
uniformly on every platform; the shebang region (first `shebang_length`
bytes) is transformed separately.

- Plumb `shebang_length` from `PrefixPlaceholder` into the offset functions
  and use it as the region boundary instead of re-deriving it from content,
  validating it equals first-0x0A-index + 1.
- Add the missing non-rewriting-target rule: on Windows (e.g. a noarch
  package) the shebang region gets plain placeholder replacement rather than
  being left untouched. On Unix it is rewritten by the shebang rules (region
  minus its trailing newline, newline copied through verbatim).
- Report producer non-conformance (in-region offset, shape/mode mismatch,
  out-of-range or unordered offsets, missing placeholder bytes, empty binary
  outer list, `#!` file without `shebang_length`) as a distinguishable
  `OffsetReplaceError::InconsistentMetadata`. The offset functions validate
  before writing anything, so `link_file` falls back to the search-based path
  (with a warning) using the still-empty destination instead of failing the
  install.
- Fix a pre-existing panic in the search-based `copy_and_replace_textual_
  placeholder`: a `#!` file with no newline fed an empty line into
  `replace_shebang`, tripping its `starts_with("#!")` assertion.
- Fix the binary test that recorded the NUL terminator one byte past the
  actual `\x00`, and align the suite with the CEP's informative test-vector
  list (short/long Unix prefixes, non-rewriting target, no trailing newline,
  empty offsets, multiple in-shebang occurrences, over-long shebang with no
  occurrence, unterminated final C string, and the fallback for non-conformant
  input).
Add read-only `offsets` and `shebang_length` getters to the py-rattler
`PrefixPlaceholder` so consumers can inspect the CEP fields
(conda#2565). `offsets` returns `None`, a `list[int]` for text-mode
files, or a `list[list[int]]` for binary-mode files (grouped by C string).
- "Check intra-doc links" (`-D rustdoc::private-intra-doc-links`): the public
  `copy_and_replace_textual_placeholder_offsets` doc linked to the private
  `replace_shebang` fn; demote it to a plain code span.
- Windows-x86_64: `test_replace_long_prefix_in_text_file_offsets` hardcoded
  `shebang_length: Some(44)`, which is wrong when the `shebang_test.txt`
  fixture is checked out with CRLF (the carriage return shifts the first
  newline to 45). Derive `shebang_length` from the file contents instead.
@chrisburr

Copy link
Copy Markdown
Contributor Author

That's a compelling arugment for rattler_vfs and it's growing on me over time. I've made the change 👍

We should not yet publish this crate automatically. This requires annotating the Cargo.toml with package.publish = false.

This is in contradiction with having rattler_vfs be a non-optional feature in rattler_bin: #2566 (comment)

For now I've gone with removing the feature and publishing the crate.

The biggest open question from my perspective is conda/ceps#179 and especially the handling of non-UTF-8 strings.

@chrisburr chrisburr marked this pull request as draft July 9, 2026 21:41
@chrisburr chrisburr changed the title feat: add rattler_fs crate feat: add rattler_vfs crate Jul 9, 2026
DagmarFiep and others added 7 commits July 10, 2026 00:04
Dagmar's foundational implementation of the on-demand virtual filesystem that
serves conda environments from the package cache. Later renamed to rattler_vfs
and hardened (mount transports, overlay, codesign, prefix-replacement parity).

(Reworded from the original "initial commit with multiple major problems still
needing fixing"; authorship preserved.)
Rename Dagmar's rattler_fs foundation to rattler_vfs and harden it into the
installable crate: FUSE/NFS/ProjFS transports, overlay, codesign, and the
ranged prefix-replacement paths (byte-identical to rattler's install path,
consuming its CEP-conformant offset APIs).
Add `rattler mount` to mount a locked environment through rattler_vfs. Gated
behind the default-on `mount` feature (opt out with --no-default-features);
the NFS transport is enabled for the bundled crate.
Add the rattler-vfs end-to-end harness: a nushell mount/verify script, a GitHub
Actions workflow, the pixi `rattler-vfs` environment, and a test-data fixture
environment for the mount tests.
Pull the shebang-region transformation out of
copy_and_replace_textual_placeholder_offsets into a reusable, public
`replace_shebang_region` helper: on Unix targets the region (minus its trailing
newline) is rewritten by the shebang rules and may collapse to
`#!/usr/bin/env <program>`; on non-rewriting targets it receives plain
placeholder replacement.

This makes the helper the single source of truth for how the shebang region is
patched, shared by install-time replacement here and rattler_vfs mount-time
ranged reads so the two stay byte-identical. Behavior is unchanged (verified by
the existing link tests).
`text_ranged_read` spliced offsets uniformly, so a mounted shebang script kept
its original (unreplaced) first line and reported the wrong size — diverging
from an install once a CEP-conformant producer excludes the shebang region from
`offsets`.

Introduce `plan_text_replacement`, which mirrors the installer: it transforms
the shebang region via the shared `replace_shebang_region` helper (collapsing an
over-long line to `#!/usr/bin/env <program>` on Unix, or plain-replacing it on
non-rewriting targets) and records the remaining occurrences as body offsets.
`text_ranged_read` now emits the transformed region followed by the body-spliced
tail, and `getattr` sizes are computed the same way.

The install-vs-mount parity tests gain shebang coverage: line kept, collapsed
for an over-long prefix, no trailing newline, multiple occurrences in the line,
only-in-shebang, and a non-rewriting (Windows) target — plus ranged reads that
cross the region/body boundary.
Two failures surfaced on the offsets work in CI:

- "Check intra-doc links" (`-D rustdoc::private-intra-doc-links`): the public
  docs for `replace_shebang_region` and
  `copy_and_replace_textual_placeholder_offsets` linked to the private
  `replace_shebang` fn. Demote those to plain code spans.
- Windows-x86_64: `test_replace_long_prefix_in_text_file_offsets` hardcoded
  `shebang_length: Some(44)`, which is wrong when the `shebang_test.txt`
  fixture is checked out with CRLF (the extra carriage return shifts the first
  newline to 45). Derive `shebang_length` from the file contents instead, so
  the test is robust to the checkout's line endings.
The Windows-only code paths never compile on the Linux/macOS CI, so three
edition-2024 / `-D warnings` errors only surfaced on the Windows job:

- projfs_adapter.rs: the `unsafe extern "system"` ProjFS callbacks perform raw
  pointer / FFI operations directly in the fn body, which Rust 2024's
  `unsafe_op_in_unsafe_fn` rejects. The whole module is inherently unsafe FFI,
  so allow the lint module-wide rather than wrapping ~30 sites.
- lib.rs `mount_nfs`: on targets without NFS support the platform block
  `bail!`s, making the success tail (`tracing::info!` + `Ok(..)`) unreachable.
  Gate that tail behind `cfg(any(macos, linux))` so it is only compiled where
  it is reachable.
- nfs_adapter.rs: `NfsMountHandle::mount_point` is only read by the macOS/Linux
  `umount` paths, so it is dead code elsewhere — allow it on non-NFS targets.
Add TextPlan::from_recorded, which builds a text replacement plan
straight from the offsets recorded in paths.json instead of scanning
the file, and use it during VirtualFS construction: one stat for the
size arithmetic plus at most shebang_length bytes read for the shebang
region. The recorded offsets are the producer's contract per the CEP
and are trusted as-is; scanning remains as the fallback for pre-CEP
packages and for metadata that fails the cheap shebang sanity check
(logged so a non-conformant producer is self-diagnosing).

Also log a warning before the macOS codesign materialization path falls
back to serving raw bytes, instead of degrading silently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants